﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Reflection;
using System.Text.Json.Serialization;
using System.Xml;
using System.Xml.Linq;

namespace LightCAD.Core
{
    public delegate void DirtyTypeChangedEventHandle(LcObject obj, DirtyType oldval, DirtyType newval);
        
    public class LcObject
    {
        // 对象本身发生变化
        public event EventHandler<ObjectChangedEventArgs> ObjectChangedBefore;
        public event EventHandler<ObjectChangedEventArgs> ObjectChangedAfter;

        // 对象的属性发生变化
        public event EventHandler<PropertyChangedEventArgs> PropertyChangedBefore;
        public event EventHandler<PropertyChangedEventArgs> PropertyChangedAfter;

        [JsonIgnore]
        public bool IsFireChangedEvent { get; set; } = true;


        //long字典比int字典慢20-30%
        [JsonInclude]
        public long Id { get; set; } = -1;

        protected LcObject()
        {
        }


        /// <summary>
        /// 初始化上下文，生成新Id，一般由内部调用，特殊情况自行调用
        /// </summary>
        /// <param name="document"></param>
        public void Initilize(LcDocument document)
        {
            this.Document = document;
            if (this.Document != null)
            {
                Id = document.IdCreator.GetObjectId();
            }
        }

        public LcPropertyGroupCollection ExtPropGroups { get; set; }

        [JsonIgnore]
        public LcDocument Document { get; internal set; }

        [JsonIgnore]
        public LcObject Parent { get; internal set; }

        [JsonIgnore]
        public string ObjectName
        {
            get { return this.GetType().FullName; }
        }
        public event DirtyTypeChangedEventHandle DirtyTypeChangedEvent;
        private DirtyType dirtyType = DirtyType.Add;
        [JsonIgnore]
        public DirtyType DirtyType
        {
            get => dirtyType;
            set
            {
                var oldVal = this.dirtyType;
                this.dirtyType = value;
                DirtyTypeChangedEvent?.Invoke(this, oldVal, value);
                if (Parent != null)
                    Parent.DirtyType = DirtyType.Change;
            }
        }
        /// <summary>
        /// 通过SetProperty及SetExtProerty触发，不触发事件，本类中使用
        /// </summary>
        /// <param name="propertyName"></param>
        /// <param name="extPropGroup"></param>
        /// <param name="extProp"></param>
        protected virtual void OnPropertyChanged(string propertyName, LcPropertyGroup extPropGroup, LcProperty extProp)
        {
        }

        /// <summary>
        /// 用反射方式设置属性
        /// </summary>
        /// <param name="name"></param>
        /// <param name="value"></param>
        /// <param name="fireChangedEvent"></param>
        public void SetProperty(string name, object value, bool fireChangedEvent = true)
        {
            var objType = this.GetType();
            if (!fireChangedEvent)
            {
                var fld = objType.GetField(name);
                if (fld != null)
                    fld.SetValue(this, value);
                else
                {
                    var prop = objType.GetProperty(name);
                    prop.SetValue(this, value);
                }

                OnPropertyChanged(name, null, null);
            }
            else
            {
                Object oldValue = null;
                PropertyInfo prop = null;
                var fld = objType.GetField(name);
                if (fld != null)
                    oldValue = fld.GetValue(this);
                else
                {
                    prop = objType.GetProperty(name);
                    if (prop != null)
                        oldValue = prop.GetValue(this);
                }

                if (oldValue == value) return;

                OnPropertyChangedBefore(name, oldValue, value);

                if (fld != null)
                    fld.SetValue(this, value);
                else if (prop != null)
                {
                    prop.SetValue(this, value);
                }
                else
                {
                    throw new ArgumentException($"Property {name} is not exist.");
                }

                OnPropertyChangedAfter(name, oldValue, value);
                OnPropertyChanged(name, null, null);
            }
        }

        /// <summary>
        /// 用反射方式获取属性值
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public object GetProperty(string name)
        {
            var objType = this.GetType();
            var fld = objType.GetField(name);
            if (fld != null)
                return fld.GetValue(this);
            else
            {
                var prop = objType.GetProperty(name);
                if (prop != null)
                    return prop.GetValue(this);
            }

            return null;
        }

        [JsonIgnore]
        public LcObjectPropertiesDefinition PropsDef { get; set; }

        private void InitExtPropDefs()
        {
            var objName = this.ObjectName;
            var doc = this.Document;
            doc.ObjectPropertiesMap.TryGetValue(objName, out LcObjectPropertiesDefinition propsDef);
            this.PropsDef = propsDef;
        }

        public LcPropertyGroup GetExtPropGroup(string groupName)
        {
            if (PropsDef == null)
            {
                InitExtPropDefs();
            }

            if (this.ExtPropGroups == null)
            {
                this.ExtPropGroups = new LcPropertyGroupCollection();
            }

            LcPropertyGroup propGrp;
            if (!this.ExtPropGroups.TryGetValue(groupName, out propGrp))
            {
                propGrp = new LcPropertyGroup
                {
                    Definition = PropsDef.GroupDefs[groupName],
                    Properties = new PropertyCollection()
                };
                this.ExtPropGroups.Add(propGrp);
            }

            return propGrp;
        }

        public LcProperty GetExtPrperty(string name)
        {
            var propGroup = this.GetExtPropGroup(string.Empty);
            return propGroup.GetProperty(name);
        }

        public LcProperty GetExtPrperty(string groupName, string name)
        {
            var propGroup = this.GetExtPropGroup(groupName);
            return propGroup.GetProperty(name);
        }

        public void SetExtPrperty(string name, object value, object? extent = null, bool fireChnagedEvent = true)
        {
            SetExtPrperty(string.Empty, name, value, extent, fireChnagedEvent);
        }

        public void SetExtPrperty(string groupName, string propName, object value, object? extent = null, bool fireChnagedEvent = true)
        {
            var propGroup = this.GetExtPropGroup(groupName);
            var prop = propGroup.GetProperty(propName);
            if (!fireChnagedEvent)
            {
                prop.Set(value, extent);
                OnPropertyChanged(null, propGroup, prop);
            }
            else
            {
                bool chg_property = (value != prop);
                if (chg_property)
                {
                    OnPropertyChangedBefore(propGroup, prop, prop, value);
                    var oldValue = prop;
                    prop.Set(value, extent);
                    OnPropertyChangedAfter(propGroup, prop, oldValue, prop);
                }

                OnPropertyChanged(null, propGroup, prop);
            }
        }

        public void OnObjectChangedBefore(ObjectChangedEventArgs args)
        {
            if (!IsFireChangedEvent) return;
            this.ObjectChangedBefore?.Invoke(this, args);
            //事件冒泡触发
            this.Parent?.ObjectChangedBefore?.Invoke(this, args);
        }

        public void OnObjectChangedAfter(ObjectChangedEventArgs args)
        {
            if (!IsFireChangedEvent) return;
            this.ObjectChangedAfter?.Invoke(this, args);

            //清除移除元素的所有挂接事件，消除内存泄漏
            if (args.Type == ObjectChangeType.Remove)
            {
                args.Target?.CLearAllEvents();
            }
            //事件冒泡触发
            this.Parent?.ObjectChangedAfter?.Invoke(this, args);
        }

        public void OnPropertyChangedBefore(string propertyName, object oldValue, object newValue)
        {
            if (!IsFireChangedEvent) return;
            if (this.Document == null) return;
            var args = new PropertyChangedEventArgs
            {
                Object = this,
                PropertyName = propertyName,
                OldValue = oldValue,
                NewValue = newValue,
            };
            this.PropertyChangedBefore?.Invoke(this, args);
            this.Document.OnPropertyChangedBefore(args);
        }

        public void OnPropertyChangedAfter(string propertyName, object oldValue, object newValue)
        {
            if (!IsFireChangedEvent) return;
            if (this.Document == null) return;
            var args = new PropertyChangedEventArgs
            {
                Object = this,
                PropertyName = propertyName,
                OldValue = oldValue,
                NewValue = newValue,
            };
            this.PropertyChangedAfter?.Invoke(this, args);
            //事件冒泡触发
            this.Parent?.PropertyChangedAfter?.Invoke(this,args);
            this.Document.OnPropertyChangedAfter(args);
        }

        public void OnPropertyChangedBefore(LcPropertyGroup propertyGroup, LcProperty property, object oldValue, object newValue)
        {
            if (!IsFireChangedEvent) return;
            if (this.Document == null) return;
            var args = new PropertyChangedEventArgs
            {
                Object = this,
                ExtPropertyGroup = propertyGroup,
                ExtProperty = property,
                OldValue = oldValue,
                NewValue = newValue,
            };
            this.PropertyChangedBefore?.Invoke(this, args);
            //事件冒泡触发
            this.Parent?.PropertyChangedBefore?.Invoke(this, args);
            this.Document.OnPropertyChangedBefore(args);
        }

        public void OnPropertyChangedAfter(LcPropertyGroup propertyGroup, LcProperty property, object oldValue, object newValue)
        {
            if (!IsFireChangedEvent) return;
            if (this.Document == null) return;
            var args = new PropertyChangedEventArgs
            {
                Object = this,
                ExtPropertyGroup = propertyGroup,
                ExtProperty = property,
                OldValue = oldValue,
                NewValue = newValue
            };
            this.PropertyChangedAfter?.Invoke(this, args);
            //事件冒泡触发
            this.Parent?.PropertyChangedAfter?.Invoke(this, args);
            this.Document.OnPropertyChangedBefore(args);
        }

        public virtual void OnRemoveBefore()
        {

        }

        public virtual void OnRemoveAfter() 
        { 
        
        }

        /// <summary>
        /// 清除对象上的所有事件，以消除事件处理导致的内存泄漏，此方法在ObjectChangedAfter Remove 会自动调用
        /// </summary>
        private void CLearAllEvents()
        {
            if (ObjectChangedBefore != null)
            {
                Delegate[] dels = ObjectChangedBefore.GetInvocationList();
                foreach (Delegate del in dels)
                {
                    object delObj = del.GetType().GetProperty("Method").GetValue(del, null);
                    string funcName = (string)delObj.GetType().GetProperty("Name").GetValue(delObj, null);
                    Console.WriteLine(funcName);
                    ObjectChangedBefore -= (EventHandler<ObjectChangedEventArgs>)del;
                }
            }

            if (ObjectChangedAfter != null)
            {
                Delegate[] dels = ObjectChangedAfter.GetInvocationList();
                foreach (Delegate del in dels)
                {
                    object delObj = del.GetType().GetProperty("Method").GetValue(del, null);
                    string funcName = (string)delObj.GetType().GetProperty("Name").GetValue(delObj, null);
                    Console.WriteLine(funcName);
                    ObjectChangedAfter -= (EventHandler<ObjectChangedEventArgs>)del;
                }
            }

            if (PropertyChangedBefore != null)
            {
                Delegate[] dels = PropertyChangedBefore.GetInvocationList();
                foreach (Delegate del in dels)
                {
                    object delObj = del.GetType().GetProperty("Method").GetValue(del, null);
                    string funcName = (string)delObj.GetType().GetProperty("Name").GetValue(delObj, null);
                    Console.WriteLine(funcName);
                    PropertyChangedBefore -= (EventHandler<PropertyChangedEventArgs>)del;
                }
            }

            if (PropertyChangedAfter != null)
            {
                Delegate[] dels = PropertyChangedAfter.GetInvocationList();
                foreach (Delegate del in dels)
                {
                    object delObj = del.GetType().GetProperty("Method").GetValue(del, null);
                    string funcName = (string)delObj.GetType().GetProperty("Name").GetValue(delObj, null);
                    Console.WriteLine(funcName);
                    PropertyChangedAfter -= (EventHandler<PropertyChangedEventArgs>)del;
                }
            }
        }

        public virtual void Copy(LcObject src)
        {
            this.Document = src.Document;
            this.PropsDef = src.PropsDef;
            if (src.ExtPropGroups != null)
            {
                this.ExtPropGroups = new LcPropertyGroupCollection();
                foreach (var pgrp in src.ExtPropGroups)
                {
                    this.ExtPropGroups.Add(pgrp.Clone());
                }
            }
        }

        public virtual void ReadObjectRefs<T>(ICollection<T> objs) where T : LcObject
        {

        }
    }
}